/* eslint-disable @typescript-eslint/no-explicit-any */

import SVGArrowDown from '../../svg/chevron-down.svg';
import SVGArrowRight from '../../svg/chevron-right.svg';
import type { Column, Contents, GridBaseProps, RowType } from '../gridBase';
import { Check } from '../input/input';
import {
	type ColumnOption,
	getChildren,
	getText,
	getValue,
	LEVEL_MARGIN,
	type ListRow,
	type ListRowExpandDetail,
	type SelectType,
} from '../list/list';
import { DATA } from '../symbol';
import { Table, type TableData } from '../table/table';
import { tableListStyle } from './tableList.css';

export type TableListData = TableData & { opt: ColumnOption; seltype: SelectType };

export type TableListProps = GridBaseProps<TableList> & {
	selectType?: SelectType;
	column?: ColumnOption;
	onRowExpand?: (this: TableList, event: ListRowExpandDetail) => void;
};

export class TableList extends Table<TableListProps, TableList> {
	override get data() {
		return this[DATA] as TableListData;
	}

	constructor(props: TableListProps) {
		super(props);
		this.data.opt = {};
		this.data.seltype = 'any';
		// this.data.config.borderBottom = 0;
		this.el.classList.add(tableListStyle);
		this.setHeadLine(false)
			.setColumnSize('scale')
			.setSelectType(props.selectType ?? 'any', false)
			.setColumn(props.column ?? {});
		this.el.addEventListener('keydown', (evt) => {
			if (evt.key === 'ArrowLeft') {
				if (!this.expandActive(false)) {
					const row = super.activeRow;
					let parent: ListRow | undefined;
					if (row && (parent = (row as ListRow).parent)) {
						const idx = this.sortedData.indexOf(parent);
						super.setActiveRow(idx, true);
					}
				}
				evt.preventDefault();
				evt.stopPropagation();
			} else if (evt.key === 'ArrowRight') {
				this.expandActive(true);
				evt.preventDefault();
				evt.stopPropagation();
			}
		});
		this.el.addEventListener('keypress', (evt) => {
			if (evt.key !== ' ') {
				return;
			}
			if (!this.activable) return;
			const idx = this.active;
			if (idx === null) {
				return;
			}
			const r = this.sortedData[idx].row;
			const checked =
				this.selectType === 'cascade' || this.selectType === 'cascade-leaf'
					? this.getRowCheckedState(r)
					: this.data.selected.has(r);
			this.checkRow(r, checked === 'half' ? true : !checked, idx);
			if (this.selectType === 'leaf' || this.selectType === 'any') {
				this.refresh('data');
			}
		});
	}

	private expandActive(expand: boolean): boolean {
		const idx = this.active;
		if (this.activable) {
			const r = this.activeRow;
			if (r !== null && idx !== null && this.getChildren(r) != null) {
				const cur = this.isExpand(r);
				if (cur !== expand) {
					this.setExpand(r, expand);
					this.props.onRowExpand?.call(this, {
						row: r,
						rowIndex: idx,
						expand,
					});
					this.refresh('data');
					return true;
				}
			}
		}
		return false;
	}

	private getRowCheckedState(row: RowType): boolean | 'half' {
		const children = this.getChildren(row);
		if (children === null) {
			return this.data.selected.has(row);
		} else {
			let checkedCount = 0;
			for (const c of children) {
				const state = this.getRowCheckedState(c);
				if (state === 'half') {
					return 'half';
				} else if (state) {
					checkedCount++;
				}
			}
			if (checkedCount === 0) {
				return false;
			} else if (checkedCount === children.length) {
				return true;
			} else {
				return 'half';
			}
		}
	}

	private setRowCheckedState(
		row: RowType,
		checked: boolean,
		selectType: 'cascade' | 'cascade-leaf',
	) {
		const children = this.getChildren(row);

		const selectable = !(this.column.nonSelectableField
			? (row as any)[this.column.nonSelectableField]
			: false);
		if (children === null) {
			if (checked) {
				if (selectable) this.data.selected.add(row);
			} else {
				this.data.selected.delete(row);
			}
		} else {
			if (selectType === 'cascade') {
				if (checked) {
					if (selectable) this.data.selected.add(row);
				} else {
					this.data.selected.delete(row);
				}
			}
			children.forEach((r) => this.setRowCheckedState(r, checked, selectType));
		}
	}

	private checkRow(r: RowType, checked: boolean, rowIdx: number) {
		const selType = this.selectType;
		if (selType === 'cascade' || selType === 'cascade-leaf') {
			this.setRowCheckedState(r, checked, selType);
			this.refresh('data');
		} else {
			if (checked) {
				this.data.selected.add(r);
			} else {
				this.data.selected.delete(r);
			}
		}
		this.props.onRowCheck?.call(this, { row: r, rowIndex: rowIdx, checked: checked });
	}

	protected override getColumns(): Column[] {
		return [
			{
				width: '100%',
				formatter: (row, _, rowIdx): Contents => {
					const level = (row as ListRow).level;
					const r = (row as ListRow).row;
					const hasChildren = this.getChildren(r) !== null;
					const arrow = (
						<span
							class="arrow"
							style={{
								marginLeft: level * LEVEL_MARGIN + 'em',
							}}
						/>
					) as HTMLSpanElement;
					if (hasChildren) {
						const isExpand = this.isExpand(r);
						arrow.innerHTML = isExpand ? SVGArrowDown : SVGArrowRight;
						arrow.addEventListener('mousedown', (ev) => {
							ev.preventDefault();
							this.el.focus();
							this.setExpand(r, !isExpand);
							this.refresh('data');
							this.props.onRowExpand?.call(this, {
								row: r,
								rowIndex: rowIdx,
								expand: !isExpand,
							});
						});
					}
					const text = getText(r, this.column);
					const cell = (<span class="content"></span>) as HTMLSpanElement;
					if (typeof this.column.formatter === 'function') {
						const contents = this.column.formatter(r, text, rowIdx);
						if (Array.isArray(contents)) {
							cell.append(...contents);
						} else {
							cell.append(contents);
						}
					} else {
						cell.append((text ?? '') + '');
					}
					let ck: HTMLInputElement | undefined;
					const selType = this.selectType;
					const selectable = !(this.column.nonSelectableField
						? (r as any)[this.column.nonSelectableField]
						: false);
					if (this.selectable && !(selType === 'leaf' && this.getChildren(r)) && selectable) {
						const checkState =
							selType === 'cascade' || selType === 'cascade-leaf'
								? this.getRowCheckedState(r)
								: this.data.selected.has(r);
						ck = (
							<Check
								checked={checkState === true}
								disabled={this.isRowDisable(row)}
								indeterminate={checkState === 'half'}
								onClick={(evt) => {
									evt.stopPropagation();
									this.checkRow(r, ck!.checked === true, rowIdx);
								}}
								onPointerDown={(evt) => {
									evt.preventDefault();
									this.el.focus();
									this.setActive(rowIdx);
								}}
							/>
						) as HTMLInputElement;
					}
					return [arrow, ck ? ck : '', cell];
				},
			},
		];
	}

	private getChildren(row: unknown): unknown[] | null {
		let children: unknown[];
		if (
			this.column.children !== undefined &&
			(children = (row as never)[this.column.children]) &&
			Array.isArray(children)
		) {
			return children;
		} else {
			return null;
		}
	}

	private isExpand(row: unknown): boolean {
		return this.column.expand !== undefined && !!(row as never)[this.column.expand];
	}

	private setExpand(row: unknown, expand: boolean) {
		if (this.column.expand !== undefined) {
			(row as Record<string | number, boolean>)[this.column.expand] = expand;
		}
	}

	protected override isRowDisable(row: unknown): boolean {
		return this.data.disableField !== null
			? !!((row as ListRow).row as any)?.[this.data.disableField]
			: false;
	}

	protected override get sortedData(): ListRow[] {
		if (this.data.sorted) {
			return this.data.sorted as ListRow[];
		}
		const addRow = (result: ListRow[], row: unknown, level: number, parent?: ListRow) => {
			const children = this.getChildren(row);
			if (children && this.isExpand(row)) {
				const listRow: ListRow = { row, level, parent };
				result.push(listRow);
				for (let i = 0; i < children.length; i++) {
					addRow(result, children[i], level + 1, listRow);
				}
			} else {
				result.push({ row, level, parent });
			}
		};
		const result: ListRow[] = [];
		for (let i = 0; i < this.data.data.length; i++) {
			addRow(result, this.data.data[i], 0);
		}
		this.data.sorted = result;
		return result;
	}

	get column(): ColumnOption {
		return this.data.opt ?? {};
	}
	setColumn(opt: ColumnOption, refresh = true): this {
		this.data.opt = opt;
		if (refresh) this.refresh('complete');
		return this;
	}

	override setColumns(_: Column[]): this {
		return this;
	}

	override get activeRow(): RowType | null {
		const r = super.activeRow;
		if (r === null) return null;
		return (r as ListRow).row;
	}

	get selectType(): SelectType {
		return this.data.seltype;
	}

	setSelectType(opt: SelectType, refresh = true): this {
		this.data.selected.clear();
		this.data.seltype = opt;
		if (refresh) this.refresh('data');
		return this;
	}

	override get selected(): RowType[] {
		return [...this.data.selected];
	}
	override setSelected(rows: RowType[]): this {
		const cols = this.column;
		const values = new Set(rows.map((value) => getValue(value, cols)));
		const find = (arr: unknown[]): unknown[] => {
			return arr.flatMap((r) => {
				const children = getChildren(r, cols);
				if (children) return [r, ...find(children)];
				else return [r];
			});
		};
		const checked = find(this.data.data).filter((row) => {
			return values.has(getValue(row, cols));
		});
		this.data.selected = new Set(checked);
		this.renderHead();
		this.genStyle();
		this.measure('selection');
		this.renderBody();
		return this;
	}

	get activeText(): string | null {
		const activeRow = this.activeRow;
		if (activeRow === null) return null;
		return getText(activeRow, this.column);
	}

	get activeValue(): unknown | null {
		const activeRow = this.activeRow;
		if (activeRow === null) return null;
		return getValue(activeRow, this.column);
	}
	setActiveValue(value: unknown, focus = false): this {
		const expandKey = this.column.expand;
		const find = (arr: unknown[]): boolean => {
			for (let i = 0; i < arr.length; i++) {
				const row = arr[i];
				if (getValue(row, this.column) === value) {
					return true;
				} else if (expandKey !== undefined && typeof row === 'object' && row !== null) {
					const children = getChildren(row, this.column);
					if (children && find(children)) {
						(row as Record<string | number, boolean>)[expandKey] = true;
						return true;
					}
				}
			}
			return false;
		};
		if (find(this.data.data)) {
			this.refresh('data');
			const idx = this.sortedData.findIndex((row) => getValue(row.row, this.column) === value);
			this.setActive(idx, focus);
		}
		return this;
	}
	onRowExpand(cb: (event: ListRowExpandDetail) => void): this {
		this.props.onRowExpand = cb;
		return this;
	}
}
